package junittests;

import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.math.BigInteger;

import org.junit.Before;
import org.junit.Test;

import domains.IntervalBound;


public class IntervalBoundTest {
	IntervalBound minusInf;
	IntervalBound plusInf;
	IntervalBound one;
	IntervalBound zero;
	IntervalBound three;
	IntervalBound minusFive;
	IntervalBound fourtytwo;

	@Before
	public void setUp(){
		minusInf = new IntervalBound(null,true,false);
		plusInf = new IntervalBound(null,false,true);
		one = new IntervalBound(BigInteger.ONE,false,false);
		zero = new IntervalBound(BigInteger.ZERO,false,false);
		three = new IntervalBound(BigInteger.valueOf(3),false,false);
		minusFive = new IntervalBound(BigInteger.valueOf(-5),false,false);
		fourtytwo = new IntervalBound(BigInteger.valueOf(42),false,false);
	}
	
	@Test
	public void testEquals(){
		IntervalBound minusInf2 = new IntervalBound(null,true,false);
		IntervalBound three2 = new IntervalBound(BigInteger.valueOf(3),false,false);
		
		assertTrue("testEquals 1",minusInf.equals(minusInf2));
		assertFalse("testEquals 2",minusInf.equals(plusInf));
		assertTrue("testEquals 3",three.equals(three2));
		assertFalse("testEquals 4",three.equals(zero));
		assertFalse("testEquals 5",three.equals(plusInf));
		
		
	}
	
	@Test
	public void testAdd(){
		IntervalBound minusTwo = new IntervalBound(BigInteger.valueOf(-2),false,false);
		IntervalBound thirtyseven = new IntervalBound(BigInteger.valueOf(37),false,false);
		
		assertEquals("add minus inf.",minusInf,minusInf.add(one));
		assertEquals("add plus inf.",plusInf,plusInf.add(three));
		// add ist nicht kommutativ
		assertNull("add commutative", plusInf.add(minusInf));
		assertNull("add commutative", minusInf.add(plusInf));
		
		assertEquals("add finite numbers",three,three.add(zero));
		assertEquals("add finite numbers 2",minusTwo,three.add(minusFive));
		assertEquals("add finite numbers 3",thirtyseven,minusFive.add(fourtytwo));
	}
	
	@Test
	public void testSub(){
		IntervalBound minusThree = new IntervalBound(BigInteger.valueOf(-3),false,false);
		IntervalBound five = new IntervalBound(BigInteger.valueOf(5),false,false);
		IntervalBound minusTwo = new IntervalBound(BigInteger.valueOf(-2),false,false);
		IntervalBound minusFourtySeven = new IntervalBound(BigInteger.valueOf(-47),false,false);
		
		assertEquals("subtract from minus inf.",minusInf,minusInf.sub(three));
		assertEquals("subtract from plus inf.",plusInf,plusInf.sub(fourtytwo));
		assertEquals("subtrat minus inf.",plusInf,zero.sub(minusInf));
		assertEquals("subtract plus inf.",minusInf,one.sub(plusInf));
		assertEquals("subtract finite numbers",minusThree,zero.sub(three));
		assertEquals("subtract finite numbers 2",fourtytwo,fourtytwo.sub(zero));
		assertEquals("subtract finite numbers 3",five,zero.sub(minusFive));
		assertEquals("subtract finite numbers 4",minusTwo,three.sub(five));
		assertEquals("subtract finite numbers 5",minusFourtySeven,minusFive.sub(fourtytwo));
	}
	
	@Test
	public void testMul(){
		IntervalBound twentyfive = new IntervalBound(BigInteger.valueOf(25),false,false);
		IntervalBound minusFifteen = new IntervalBound(BigInteger.valueOf(-15),false,false);
		
		assertEquals("mul (-inf,inf)",minusInf,minusInf.mul(plusInf));
		assertEquals("mul (-inf,-inf)",plusInf,minusInf.mul(minusInf));
		assertEquals("mul (inf,inf)",plusInf,plusInf.mul(plusInf));
		assertEquals("mul (pos. finite number,inf)",plusInf,three.mul(plusInf));
		// 0 * inf undefined
		assertNull("mul (zero,inf)",zero.mul(plusInf));
		assertNull("mul (zero,inf)",zero.mul(minusInf));

		assertEquals("mul (neg. finite number,inf)",minusInf,minusFive.mul(plusInf));
		assertEquals("mul (pos. finite number,-inf)",minusInf,one.mul(minusInf));
		assertEquals("mul (neg. finite number,-inf)",plusInf,minusFive.mul(minusInf));
		assertEquals("mul (zero,pos. finite number)",zero,zero.mul(three));
		assertEquals("mul (neg. finite number,zero)",zero,minusFive.mul(zero));
		assertEquals("mul (pos.finite number,pos. finite number)",three,three.mul(one));
		assertEquals("mul (neg. finite number,neg. finite number)",twentyfive,minusFive.mul(minusFive));
		assertEquals("mul (pos. finite number,neg. finite number)",minusFifteen,three.mul(minusFive));
	}
	
	@Test
	public void testDiv(){
		// Corner cases
		assertNull("div (inf,inf)",plusInf.div(plusInf));
		assertNull("div (-inf,-inf)",minusInf.div(minusInf));
		assertNull("div (inf,-inf)",plusInf.div(minusInf));
		assertNull("div (-inf,inf)",minusInf.div(plusInf));
		
		assertEquals("div (inf,pos. finite number)",plusInf,plusInf.div(three));
		assertEquals("div (pos. finite number,inf)",zero,three.div(plusInf));
		assertEquals("div (inf,neg. finite number)",minusInf,plusInf.div(minusFive));
		assertEquals("div (neg. finite number,inf)",zero,minusFive.div(plusInf));
		assertEquals("div (-inf,pos. finite number)",minusInf,minusInf.div(three));
		assertEquals("div (pos. finite number,-inf)",zero,three.div(minusInf));
		assertEquals("div (-inf,neg. finite number)",plusInf,minusInf.div(minusFive));
		assertEquals("div (neg. finite number,-inf)",zero,minusFive.div(minusInf));
		assertEquals("div (zero,inf)",zero,zero.div(plusInf));
		// division by zero
		assertNull("div (inf,zero)",plusInf.div(zero));
		assertNull("div (-inf,zero)",minusInf.div(zero));
		assertNull("div (zero,zero)",zero.div(zero));
		assertNull("div (pos. finite number,zero)",three.div(zero));
		assertNull("div (neg. finite number,zero",minusFive.div(zero));
		
		assertEquals("div (zero,-inf)",zero,zero.div(minusInf));
		assertEquals("div (zero,pos. finite number)",zero,zero.div(three));
		assertEquals("div (zero,neg. finite number)",zero,zero.div(minusFive));
		
		IntervalBound minusOne = new IntervalBound(BigInteger.valueOf(-1),false,false);
		IntervalBound minusTwo = new IntervalBound(BigInteger.valueOf(-2),false,false);
		IntervalBound two = new IntervalBound(BigInteger.valueOf(2),false,false);
		
		assertEquals("div (pos. finite number, pos. finite number)",zero,one.div(three));
		assertEquals("div (pos. finite number, neg. finite number)",zero,three.div(minusFive));
		assertEquals("div (neg. finite number, pos. finite number)",minusOne,minusFive.div(three));
		assertEquals("div (neg. finite number, neg. finite number)",two,minusFive.div(minusTwo));
	}
	
	@Test
	public void testNegate(){
		IntervalBound minusThree = new IntervalBound(BigInteger.valueOf(-3),false,false);
		IntervalBound five = new IntervalBound(BigInteger.valueOf(5),false,false);
		
		assertEquals("negate -inf.",plusInf,minusInf.negate());
		assertEquals("negate inf.",minusInf,plusInf.negate());
		assertEquals("negate 0",zero,zero.negate());
		assertEquals("negate pos. number",minusThree,three.negate());
		assertEquals("negate neg. number",five,minusFive.negate());
	}
	
	@Test
	public void testEq(){
		IntervalBound three2 = new IntervalBound(BigInteger.valueOf(3),false,false);
		assertFalse("eq (-inf,+inf)",minusInf.eq(plusInf));
		
		// no suggestions
		assertFalse("eq (-inf,-inf)",minusInf.eq(minusInf));
		assertFalse("eq (+inf,+inf)",plusInf.eq(plusInf));
		
		assertFalse("eq (+inf,finite number)",plusInf.eq(three));
		assertFalse("eq (finite number,-inf)",minusFive.eq(minusInf));
		assertFalse("eq (different finite numbers)",zero.eq(fourtytwo));
		assertTrue("eq (equal finite numbers)",three.eq(three2));
	}
	
	@Test
	public void testLt(){
		// no suggestions
		assertFalse("lt (-inf,-inf)",minusInf.lt(minusInf));
		assertFalse("lt (inf,inf)",plusInf.lt(plusInf));
		
		assertTrue("lt (-inf,inf)",minusInf.lt(plusInf));
		assertFalse("lt (inf,-inf)",plusInf.lt(minusInf));
		assertTrue("lt (finite number,inf)",three.lt(plusInf));
		assertFalse("lt (finite number,-inf)",minusFive.lt(minusInf));
		assertFalse("lt (two equal finite numbers)",three.lt(three));
		assertTrue("lt 1",three.lt(fourtytwo));
		assertFalse("lt 2",three.lt(zero));
		assertTrue("lt 3",minusFive.lt(zero));
	}
	
	@Test
	public void testGt(){
		assertTrue("gt (inf,-inf)",plusInf.gt(minusInf));
		// inf > inf gilt nicht, eigentlich undefiniert, false sollte aber ok sein
		// da die operation gt auf intervallen dann [0,1] liefern sollte wenn in undefinerten fällen false zurückgegebn wird
		assertFalse("gt (inf,inf)",plusInf.gt(plusInf));
		assertFalse("gt (-inf,-inf)", minusInf.gt(minusInf));
		
		assertFalse("gt (-inf,inf)",minusInf.gt(plusInf));
		assertTrue("gt (finite number,-inf)",zero.gt(minusInf));
		assertFalse("gt (finite number,inf)",three.gt(plusInf));
		assertFalse("gt (two equal finite numbers)",fourtytwo.gt(fourtytwo));
		assertTrue("gt 1",three.gt(minusFive));
		assertTrue("gt 2",zero.gt(minusFive));
		assertFalse("gt 3",three.gt(fourtytwo));
	}
	
	@Test
	public void testNe(){
		// cannot make any suggestions
		assertFalse("ne (inf,inf)",plusInf.ne(plusInf));
		assertFalse("ne (-inf,-inf)",minusInf.ne(minusInf));
		
		assertTrue("ne (inf,-inf)",plusInf.ne(minusInf));
		assertTrue("ne (finite number,-inf)",zero.ne(minusInf));
		assertTrue("ne (inf,finite number)",plusInf.ne(fourtytwo));
		assertTrue("ne (two different finite numbers)",three.ne(minusFive));
		assertFalse("ne (two equal finite numbers",zero.ne(zero));
	}
	
	@Test
	public void testLe(){
		assertTrue("le (-inf,inf)",minusInf.le(plusInf));
		// same here
		assertFalse("le (-inf,-inf)",minusInf.le(minusInf));
		assertFalse("le (inf,inf)",plusInf.le(plusInf));
		
		assertTrue("le (finite number,inf)",fourtytwo.le(plusInf));
		assertTrue("le (-inf,finite number)",minusInf.le(minusFive));
		assertTrue("le (two equal finite numbers)",three.le(three));
		assertTrue("le (two different finite numbers)",zero.le(one));
	}
	
	@Test
	public void testGe(){
		assertTrue("ge (inf,-inf)",plusInf.ge(minusInf));
		// inf >= inf, -inf >= -inf -> false (eigtl. undef.)
		assertFalse("ge (inf,inf)",plusInf.ge(plusInf));
		assertFalse("ge (-inf,-inf)",minusInf.ge(minusInf));
		
		assertTrue("ge (finite number,-inf)",one.ge(minusInf));
		assertFalse("ge (finite number,inf",fourtytwo.ge(plusInf));
		assertTrue("ge (two equal finite numbers)",one.ge(one));
		assertTrue("ge (two different finite numbers)",one.ge(minusFive));
		assertFalse("ge (two different finite numbers) 2",zero.ge(fourtytwo));
	}
	
	@Test
	public void testMin(){
		assertEquals("min (inf,-inf)",minusInf,IntervalBound.min(plusInf,minusInf));
		assertEquals("min (-inf,-inf)",minusInf,IntervalBound.min(minusInf,minusInf));
		assertEquals("min (inf,inf)",plusInf,IntervalBound.min(plusInf,plusInf));
		assertEquals("min (inf,finite number)",three,IntervalBound.min(three,plusInf));
		// -inf ->
		assertEquals("min (-inf,finite number)",minusInf,IntervalBound.min(minusInf,zero));
		
		assertEquals("min (two different finite numbers)",three,IntervalBound.min(three,fourtytwo));
		assertEquals("min (two equal finite numbers)",minusFive,IntervalBound.min(minusFive, minusFive));
	}
	
	@Test
	public void testMax(){
		assertEquals("max (inf,-inf)",plusInf,IntervalBound.max(plusInf, minusInf));
		assertEquals("max (-inf,-inf)",minusInf,IntervalBound.max(minusInf, minusInf));
		assertEquals("max (inf,inf)",plusInf,IntervalBound.max(plusInf, plusInf));
		assertEquals("max (finite number,inf)",plusInf,IntervalBound.max(three,plusInf));
		assertEquals("max (inf,finite number)",plusInf,IntervalBound.max(plusInf, three));
		assertEquals("max (finite number,-inf)",minusFive,IntervalBound.max(minusFive,minusInf));
		assertEquals("max (-inf,finite number)",minusFive,IntervalBound.max(minusInf, minusFive));
		assertEquals("max (two different finite numbers)",three,IntervalBound.max(three, zero));
		assertEquals("max (two equal finite numbers)",one,IntervalBound.max(one, one));
	}
}
